/*
 * File      : stm32f4_rtc.c
 * This file is stm32f4 rtc driver part of RT-Thread RTOS
 * COPYRIGHT (C) 2009, RT-Thread Development Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2015-09-29     FlyM	       the first version.
 */
#include <time.h>
#include <rtthread.h>
#include <stm32f4xx.h>
#include "driver/stm32f4_rtc.h"
#include "stm32f4xx_pwr.h"

static int get_week(int year, int month, int day)
{
	if(month == 1 || month == 2) {
		year -= 1;
		month += 12;
	}

	return (day + 1 + 2 * month + 3 * (month + 1) / 5 + year + (year / 4) - year / 100 + year / 400) % 7;
}

static struct rt_device rtc;
static rt_err_t rt_rtc_open(rt_device_t dev, rt_uint16_t oflag)
{
	if(dev->rx_indicate != RT_NULL) {
		/* Open Interrupt */
	}

	return RT_EOK;
}

static rt_size_t rt_rtc_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)
{
	return 0;
}

static rt_err_t rt_rtc_control(rt_device_t dev, int cmd, void* args)
{
	rt_time_t* time;
	RT_ASSERT(dev != RT_NULL);

	switch(cmd) {
		case RT_DEVICE_CTRL_RTC_GET_TIME: {
			struct tm ct;
			RTC_TimeTypeDef	t;
			RTC_DateTypeDef	d;

			rt_memset(&ct, 0, sizeof(struct tm));
			time = (rt_time_t*)args;
			/* read device */
			RTC_GetDate(RTC_Format_BIN, &d);
			RTC_GetTime(RTC_Format_BIN, &t);

			ct.tm_year = d.RTC_Year + 100;
			ct.tm_mon = d.RTC_Month - 1;
			ct.tm_mday = d.RTC_Date;
			ct.tm_wday = d.RTC_WeekDay;

			ct.tm_hour = t.RTC_Hours;
			ct.tm_min = t.RTC_Minutes;
			ct.tm_sec = t.RTC_Seconds;

			*time = mktime(&ct);
		}
		break;

		case RT_DEVICE_CTRL_RTC_SET_TIME: {
			struct tm* ct;
			struct tm tm_new;
			time_t* time;
			RTC_TimeTypeDef  RTC_TimeStructure;
			RTC_InitTypeDef  RTC_InitStructure;
			RTC_DateTypeDef  RTC_DateStructure;

			time = (time_t*)args;
			/* lock scheduler. */
			rt_enter_critical();
			/* converts calendar time time into local time. */
			ct = localtime(time);
			/* copy the statically located variable */
			rt_memcpy(&tm_new, ct, sizeof(struct tm));
			/* unlock scheduler. */
			rt_exit_critical();
			/* Enable the PWR clock */
			RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
			/* Allow access to RTC */
			PWR_BackupAccessCmd(ENABLE);
			/* ck_spre(1Hz) = RTCCLK(LSI) /(uwAsynchPrediv + 1)*(uwSynchPrediv + 1)*/
			//uwSynchPrediv = 0xFF;
			//uwAsynchPrediv = 0x7F;
			/* Configure the RTC data register and RTC prescaler */
			RTC_InitStructure.RTC_AsynchPrediv = 0x7F;
			RTC_InitStructure.RTC_SynchPrediv = 0xFF;
			RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;
			RTC_Init(&RTC_InitStructure);
			/* Set the date */
			RTC_DateStructure.RTC_Year = tm_new.tm_year - 100;
			RTC_DateStructure.RTC_Month = tm_new.tm_mon + 1;
			RTC_DateStructure.RTC_Date = tm_new.tm_mday;
			RTC_DateStructure.RTC_WeekDay = get_week(tm_new.tm_year + 1900, tm_new.tm_mon + 1, tm_new.tm_mday);
			RTC_SetDate(RTC_Format_BIN, &RTC_DateStructure);

			/* Set the time*/
			if(tm_new.tm_hour > 11) {
				RTC_TimeStructure.RTC_H12 = RTC_H12_PM;
			} else {
				RTC_TimeStructure.RTC_H12 = RTC_H12_AM;
			}

			RTC_TimeStructure.RTC_Hours   = tm_new.tm_hour;
			RTC_TimeStructure.RTC_Minutes = tm_new.tm_min;
			RTC_TimeStructure.RTC_Seconds = tm_new.tm_sec;

			RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure);

			/* Indicator for the RTC configuration */
			RTC_WriteBackupRegister(RTC_BKP_DR0, 0x32F2);
		}
		break;
	}

	return RT_EOK;
}

/*******************************************************************************
* Function Name  : RTC_Configuration
* Description    : Configures the RTC.
* Input          : None
* Output         : None
* Return         : 0 reday,-1 error.
*******************************************************************************/
int RTC_Configuration(void)
{
	/* Enable the PWR clock */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);

	/* Allow access to RTC */
	PWR_BackupAccessCmd(ENABLE);

#if defined (RTC_CLOCK_SOURCE_LSI)  /* LSI used as RTC source clock*/
	/* The RTC Clock may varies due to LSI frequency dispersion */
	/* Enable the LSI OSC */
	RCC_LSICmd(ENABLE);

	/* Wait till LSI is ready */
	while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET) {
	}

	/* Select the RTC Clock Source */
	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);

	/* ck_spre(1Hz) = RTCCLK(LSI) /(uwAsynchPrediv + 1)*(uwSynchPrediv + 1)*/
	//uwSynchPrediv = 0xFF;
	//uwAsynchPrediv = 0x7F;

#elif defined (RTC_CLOCK_SOURCE_LSE) /* LSE used as RTC source clock */
	/* Enable the LSE OSC */
	RCC_LSEConfig(RCC_LSE_ON);

	/* Wait till LSE is ready */
	while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
	}

	/* Select the RTC Clock Source */
	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);

#else
#error Please select the RTC Clock source inside the main.c file
#endif /* RTC_CLOCK_SOURCE_LSI */

	/* Enable the RTC Clock */
	RCC_RTCCLKCmd(ENABLE);

	/* Wait for RTC APB registers synchronisation */
	RTC_WaitForSynchro();

	return 0;
}

void rt_hw_rtc_init(void)
{
	rtc.type	= RT_Device_Class_RTC;

	if(RTC_ReadBackupRegister(RTC_BKP_DR0) != 0x32F2) {
		rt_kprintf("rtc is not configured\n");
		rt_kprintf("please configure with set_date and set_time\n");

		if(RTC_Configuration() != 0) {
			rt_kprintf("rtc configure fail...\r\n");
			return ;
		}
	} else {
		/* Wait for RTC registers synchronization */
		RTC_WaitForSynchro();
	}

	/* register rtc device */
	rtc.init 	= RT_NULL;
	rtc.open 	= rt_rtc_open;
	rtc.close	= RT_NULL;
	rtc.read 	= rt_rtc_read;
	rtc.write	= RT_NULL;
	rtc.control = rt_rtc_control;

	/* no private */
	rtc.user_data = RT_NULL;

	rt_device_register(&rtc, "rtc", RT_DEVICE_FLAG_RDWR);

	return;
}

#ifdef RT_USING_FINSH
#include "finsh.h"
void show_time(void)
{
	RTC_TimeTypeDef	t;
	RTC_DateTypeDef	d;
	/* Get the current Date */
	RTC_GetDate(RTC_Format_BIN, &d);
	/* Get the current Time */
	RTC_GetTime(RTC_Format_BIN, &t);
	/* Display time Format : hh:mm:ss */
	rt_kprintf("Now Time = 20%02d %02d %02d[%02d]-%0.2d:%0.2d:%0.2d \r\n", \
	           d.RTC_Year, d.RTC_Month, d.RTC_Date, d.RTC_WeekDay, t.RTC_Hours, t.RTC_Minutes, t.RTC_Seconds);
}

FINSH_FUNCTION_EXPORT(show_time, show current time.)
#endif


